#include "easylog.h"

#ifdef _EASYLOG
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#ifdef _EASYLOG_OUT_COLOR
/**
 * @brief EASYLOG OUTPUT LOG COLOR
 * 
 */
static const char *Log_Color_Output[] = {
    [ELOG_LVL_ASSERT]  = ELOG_COLOR_ASSERT,
    [ELOG_LVL_ERROR]   = ELOG_COLOR_ERROR,
    [ELOG_LVL_WARN]    = ELOG_COLOR_WARN,
    [ELOG_LVL_INFO]    = ELOG_COLOR_INFO,
    [ELOG_LVL_DEBUG]   = ELOG_COLOR_DEBUG,
    [ELOG_LVL_VERBOSE] = ELOG_COLOR_VERBOSE,
};
#endif
/**
 * @brief EASYLOG LOG OUTPUT LEVEL
 * 
 */
static const char *Log_Level_Ouput[] = {
    [ELOG_LVL_ASSERT]  = "A ",
    [ELOG_LVL_ERROR]   = "E ",
    [ELOG_LVL_WARN]    = "W ",
    [ELOG_LVL_INFO]    = "I ",
    [ELOG_LVL_DEBUG]   = "D ",
    [ELOG_LVL_VERBOSE] = "V ",
};
static bool EasylogStatus = true;
static char Log_Buffer[_EASYLOG_BUFFER_SIZE] = {0};
static uint8_t Log_Default_Level = _EASYLOG_OUT_LEVEL;
static eEasylogPut Log_CurPut = NULL;
#ifdef _EASYLOG_TIME
static eEasylogTimer Log_CurTimer = NULL;
static char Log_Time_Buffer[_EASYLOG_TIMER_MAX_SIZE] = {0};
#endif

/**
 * @brief eEasylog output interface
 * 
 * @param log log info
 * @param size log size
 */
static void eEasylogPortOutput(char *log,size_t size){
    if(Log_CurPut != NULL){
        Log_CurPut(log,size);
        return;
    }
    printf("%.*s",size,log);
}

#ifdef _EASYLOG_TIME
/**
 * @brief get curent timer base
 * 
 * @return uint32_t 
 */
static uint32_t eEasylogGetTimer(void){
    if(Log_CurTimer){
       return Log_CurTimer();
    }
    return 0;
}

/**
 * @brief register log timer source
 * 
 * @param timer 
 */
void eEasylogTimerRegister(eEasylogTimer timer){
    Log_CurTimer = timer;
}
#endif
/**
 * @brief eEasylog Register Function
 * 
 * @param put func
 */
void eEasylogPutRegister(eEasylogPut put){
    Log_CurPut = put;
}

/**
 * @brief eEasylog Output Log Level Set
 * 
 * @param level Log Level
 */
void eEasylogLevelSet(uint8_t level){
    Log_Default_Level = level;
}

/**
 * @brief enable esaylog output
 * 
 */
void eEsaylogEnable(void){
    EasylogStatus = true;
}

/**
 * @brief disable easylog output
 * 
 */
void eEasylogDisable(void){
    EasylogStatus = false;
}
/**
 * @brief eEasylog  String Copy
 * 
 * @param cur_len current lens
 * @param dst destination buffer
 * @param src source date buffer
 * @return size_t 
 */
static size_t eEasyLogStrcpy(size_t cur_len,char *dst,const char *src){
    const char *src_old = src;

    _ASSERT(dst);
    _ASSERT(src);

    while (*src != 0) {
        /* make sure destination has enough space */
        if (cur_len++ < _EASYLOG_BUFFER_SIZE) {
            *dst++ = *src++;
        } else {
            break;
        }
    }
    return src - src_old;
}

/**
 * @brief eEasylog output normal ingo
 * 
 * @param format 
 * @param ... 
 */
void eEasylogOutputNormal(const char *format,...){
    if(EasylogStatus != true){
        return;
    }
    int lens = 0;
    va_list args;
    va_start(args,format);
    lens = vsnprintf(Log_Buffer,_EASYLOG_BUFFER_SIZE,format,args);
    va_end(args);
    eEasylogPortOutput(Log_Buffer,lens);
}

/**
 * @brief eEasylog log output
 * 
 * @param level log Level
 * @param tag  log Tag
 * @param file  log File Path
 * @param func  log Function Name
 * @param line  log Line
 * @param format 
 * @param ... 
 */
void eEasylogOutput(uint8_t level,const char *tag,const char *file,const char *func,
                    const long line,const char *format,...){
    if(EasylogStatus != true){
        return;
    }
    size_t tag_len = strlen(tag), log_len = 0, newline_len = strlen(_NEWLINE);
    char line_num[_EASYLOG_LINE_MAX_SIZE + 1] = {0};
    char tag_space[_EASYLOG_SPACE_MAX_SIZE + 1] = {0};

	if(level > Log_Default_Level){
		return;
	}
    va_list args;
    va_start(args,format);

    int fmt_result;

    /*start sign and color info*/
#ifdef _EASYLOG_OUT_COLOR
    log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,CSI_START);
    log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,Log_Color_Output[level]);
#endif

    /*package tag info*/
    if(tag != NULL){
        /*package level info*/
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,Log_Level_Ouput[level]);
        #ifdef _EASYLOG_TIME
        sprintf(Log_Time_Buffer,"(%d) ",eEasylogGetTimer());
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,Log_Time_Buffer);
        #endif
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,tag);
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,":");
        if(tag_len <= _EASYLOG_SPACE_MAX_SIZE){
            memset(tag_space,' ',_EASYLOG_SPACE_MAX_SIZE - tag_len);
        }
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len," ");
    }
    if((file != NULL) || (line != 0) || (func != NULL)){
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,"(");
        if(file != NULL){
            /*copy file name*/
            log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,file);
            if(func != NULL){
                log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,":");
            }else if(line != 0){
                log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len," ");
            } 
        }
        
        /*copy line */
        if(line != 0){
            snprintf(line_num, _EASYLOG_LINE_MAX_SIZE, "%ld", line);
            log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,line_num);
            if(func != NULL){
                log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len," ");
            }
        }

        /*copy function*/
        if(func != NULL){
            log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,func);
        }
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,")");
    }
    
    fmt_result = vsnprintf(Log_Buffer + log_len,_EASYLOG_BUFFER_SIZE - log_len,format,args);
    va_end(args);

    /* calculate log length */
    if((log_len + fmt_result <= _EASYLOG_BUFFER_SIZE) && (fmt_result > -1)){
        log_len += fmt_result;
    }else{
        log_len = _EASYLOG_BUFFER_SIZE;
    }
    
    /*end overflow and newline check*/
    if (log_len + (sizeof(CSI_END) - 1) + newline_len > _EASYLOG_BUFFER_SIZE) {
        /* using max length */
        log_len = _EASYLOG_BUFFER_SIZE;
        /* reserve some space for CSI end sign */
        log_len -= (sizeof(CSI_END) - 1);
        log_len -= newline_len;
    }
#ifdef _EASYLOG_OUT_COLOR
    log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,CSI_END);
#endif
    if(tag != NULL){
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,_NEWLINE);
    }
    eEasylogPortOutput(Log_Buffer,log_len);                   
}

/**
 * @brief eEasylog output Hex Date
 * 
 * @param level  log Level
 * @param tag  log Tag
 * @param date log Hex Date
 * @param size  log output size
 */
void eEasylogHexOutput(uint8_t level,const char *tag,void *date,uint16_t size){
    if(EasylogStatus != true){
        return;
    }
    size_t tag_len = strlen(tag), log_len = 0, newline_len = strlen(_NEWLINE);
    char tag_space[_EASYLOG_SPACE_MAX_SIZE + 1] = {0};

	if(level > Log_Default_Level){
		return;
	}
    /*start sign and color info*/
#ifdef _EASYLOG_OUT_COLOR
    log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,CSI_START);
    log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,Log_Color_Output[level]);
#endif
    /*package tag info*/
    if(tag != NULL){
        /*package level info*/
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,Log_Level_Ouput[level]);
        #ifdef _EASYLOG_TIME
        sprintf(Log_Time_Buffer,"(%d) ",eEasylogGetTimer());
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,Log_Time_Buffer);
        #endif
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,tag);
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,":");
        if(tag_len <= _EASYLOG_SPACE_MAX_SIZE){
            memset(tag_space,' ',_EASYLOG_SPACE_MAX_SIZE - tag_len);
        }
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len," ");
    }
    
    for(int i = 0;(i < size) && (i < _EASYLOG_BUFFER_SIZE / 2); i ++){
        sprintf((Log_Buffer + log_len) + i * 2,"%.2X",((char *)date)[i]);
    }
    log_len += size * 2;
    /*end overflow and newline check*/
    if (log_len + (sizeof(CSI_END) - 1) + newline_len > _EASYLOG_BUFFER_SIZE) {
        /* using max length */
        log_len = _EASYLOG_BUFFER_SIZE;
        /* reserve some space for CSI end sign */
        log_len -= (sizeof(CSI_END) - 1);
        log_len -= newline_len;
    }
#ifdef _EASYLOG_OUT_COLOR
    log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,CSI_END);
#endif
    if(tag != NULL){
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,_NEWLINE);
    }
    eEasylogPortOutput(Log_Buffer,log_len);
}
/**
 * dump the hex format data to log
 *
 * @param name name for hex object, it will show on log header
 * @param width hex number for every line, such as: 16, 32
 * @param buf hex buffer
 * @param size buffer size
 */
void eEasylogHexDump(const char *name, uint8_t width, const void *buf, uint16_t size){
    #define IS_PRINT(ch)       ((unsigned int)((ch) - ' ') < 127u - ' ')
    if(EasylogStatus != true){
        return;
    }
    uint16_t i,j;
    uint16_t log_len = 0;
    const uint8_t *buffer_p = buf;
    char hex_dump_string[8] = {0};
    char head_buffer[16] = {0};
    int fmt_result = 0;

    for(i = 0;i < size; i += width){
#ifdef _EASYLOG_OUT_COLOR
        sprintf(head_buffer,"%s%s",CSI_START,Log_Color_Output[ELOG_LVL_DEBUG]);
#endif
#ifdef _EASYLOG_TIME
        fmt_result = snprintf(Log_Buffer, _EASYLOG_BUFFER_SIZE, "%sH (%d) %s: %04X-%04X: ", head_buffer,eEasylogGetTimer(),name, i, i + width - 1);
#else
        fmt_result = snprintf(Log_Buffer, _EASYLOG_BUFFER_SIZE, "%sH %s: %04X-%04X: ", head_buffer,name, i, i + width - 1);
#endif
        /* calculate log length */
        if ((fmt_result > -1) && (fmt_result <= _EASYLOG_BUFFER_SIZE)) {
            log_len = fmt_result;
        } else {
            log_len = _EASYLOG_BUFFER_SIZE;
        }
        /* dump hex */
        for (j = 0; j < width; j++) {
            if (i + j < size) {
                snprintf(hex_dump_string, sizeof(hex_dump_string), "%02X ", buffer_p[i + j]);
            } else {
                strncpy(hex_dump_string, "   ", sizeof(hex_dump_string));
            }
            log_len += eEasyLogStrcpy(log_len, Log_Buffer + log_len, hex_dump_string);
            if ((j + 1) % 8 == 0) {
                log_len += eEasyLogStrcpy(log_len, Log_Buffer + log_len, " ");
            }
        }
        log_len += eEasyLogStrcpy(log_len, Log_Buffer + log_len, "  ");
        /* dump char for hex */
        for (j = 0; j < width; j++) {
            if (i + j < size) {
                snprintf(hex_dump_string, sizeof(hex_dump_string), "%c", IS_PRINT(buffer_p[i + j]) ? buffer_p[i + j] : '.');
                log_len += eEasyLogStrcpy(log_len, Log_Buffer + log_len, hex_dump_string);
            }
        }
        /* overflow check and reserve some space for newline sign */
        if (log_len + strlen(_NEWLINE) > _EASYLOG_BUFFER_SIZE) {
            log_len = _EASYLOG_BUFFER_SIZE - strlen(_NEWLINE);
        }
#ifdef _EASYLOG_OUT_COLOR
        log_len += eEasyLogStrcpy(log_len,Log_Buffer + log_len,CSI_END);
#endif
        /* package newline sign */
        log_len += eEasyLogStrcpy(log_len, Log_Buffer + log_len, _NEWLINE);
        eEasylogPortOutput(Log_Buffer,log_len);
    }
}
#endif
